package com.alogic.sign;

import com.alogic.auth.CookieManager;
import com.alogic.auth.Session;
import com.alogic.auth.SessionManager;
import com.alogic.auth.SessionManagerFactory;
import com.alogic.cas.CasConstants;
import com.alogic.xscript.Logiclet;
import com.alogic.xscript.LogicletContext;
import com.alogic.xscript.Script;
import com.alogic.xscript.doc.XsObject;
import com.alogic.xscript.doc.json.JsonObject;
import com.anysoft.util.*;
import com.anysoft.util.resource.ResourceFactory;
import com.anysoft.webloader.FilterConfigProperties;
import com.anysoft.webloader.HttpClientTool;
import com.logicbus.backend.Context;
import com.logicbus.backend.server.http.HttpContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

/**
 * sign框架过滤器
 *
 * @since 1.6.13.7 [20200720 duanyy]
 */
public class SignFilter implements Filter, Configurable, XMLConfigurable, CasConstants {
    /**
     * a logger of slf4j
     */
    protected final static Logger LOG = LoggerFactory.getLogger(SignFilter.class);

    /**
     * 缺省配置文件
     */
    protected static final String DEFAULT = "java:///com/alogic/sign/default.xml#" + SignFilter.class.getName();

    /**
     * 编码
     */
    protected String encoding = "utf-8";

    /**
     * command的前缀
     */
    protected String cmdPrefix = "/sign";

    /**
     * 内部跳转URL的参数
     */
    protected String arguReturn = ARGU_RETURNURL;

    protected String arguNextUrl = "$nextUrl";

    protected String sessionGroup = "$sign";

    /**
     * 登录地址
     */
    protected String loginURL = "/login";

    /**
     * 注销地址
     */
    protected String logoutURL = "/logout";

    /**
     * 注册地址
     */
    protected String registerURL = "/register";

    /**
     * 帐号绑定地址
     */
    protected String bindURL = "/bind";

    protected String mainURL = "/";

    /**
     * 进行登录跳转之前执行脚本
     */
    protected Logiclet onSignin = null;

    /**
     * 进行注册跳转之前执行脚本
     */
    protected Logiclet onSignup = null;

    /**
     * 进行注销跳转之前执行脚本
     */
    protected Logiclet onSignout = null;

    /**
     * 进行登录成功跳转之前执行脚本
     */
    protected Logiclet onSignok = null;

    /**
     * 进行绑定跳转之前执行脚本
     */
    protected Logiclet onSignbind = null;

    protected HttpClientTool httpClientTool = null;

    @Override
    public void configure(Properties props) {
        cmdPrefix = PropertiesConstants.getString(props, "cmdPrefix",cmdPrefix);
        arguReturn = PropertiesConstants.getString(props,"argu.returnUrl",arguReturn);
        arguNextUrl = PropertiesConstants.getString(props,"argu.nextUrl",arguNextUrl);

        loginURL = PropertiesConstants.getString(props, "url.in", loginURL);
        logoutURL = PropertiesConstants.getString(props, "url.out", logoutURL);
        registerURL = PropertiesConstants.getString(props, "url.up", registerURL);
        bindURL = PropertiesConstants.getString(props, "url.bind", bindURL);
        mainURL = PropertiesConstants.getString(props, "url.main", mainURL);

        encoding = PropertiesConstants.getString(props,"http.encoding",encoding);

        sessionGroup = PropertiesConstants.getString(props, "sessionGroup",sessionGroup);

        httpClientTool = Settings.getToolkit(HttpClientTool.class);
    }

    @Override
    public void configure(Element e, Properties p) {
        Properties props = new XmlElementProperties(e,p);
        configure(props);
        Element elem = XmlTools.getFirstElementByPath(e, "on-sign-in");
        if (elem != null){
            onSignin = Script.create(elem, props);
        }
        elem = XmlTools.getFirstElementByPath(e, "on-sign-out");
        if (elem != null){
            onSignout = Script.create(elem, props);
        }
        elem = XmlTools.getFirstElementByPath(e, "on-sign-up");
        if (elem != null){
            onSignup = Script.create(elem, props);
        }
        elem = XmlTools.getFirstElementByPath(e, "on-sign-ok");
        if (elem != null){
            onSignok = Script.create(elem, props);
        }
        elem = XmlTools.getFirstElementByPath(e, "on-sign-bind");
        if (elem != null){
            onSignbind = Script.create(elem, props);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        FilterConfigProperties props = new FilterConfigProperties(filterConfig);
        String master = PropertiesConstants.getString(props, "sign.master", DEFAULT);
        String secondary = PropertiesConstants.getString(props, "sign.secondary", DEFAULT);
        ResourceFactory rf = Settings.getResourceFactory();

        InputStream in = null;
        try {
            in = rf.load(master, secondary, null);
            Document doc = XmlTools.loadFromInputStream(in);
            if (doc != null){
                configure(doc.getDocumentElement(), props);
            }
        }catch (Exception ex){
            LOG.error("Can not init sign with file : " + master);
        }finally{
            IOTools.close(in);
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpReq = (HttpServletRequest)request;
        HttpServletResponse httpResp = (HttpServletResponse)response;
        SessionManager sm = SessionManagerFactory.getDefault();
        Session session = sm.getSession(httpReq,httpResp,true);

        try {
            String cmd = getCommand(httpReq.getRequestURI());
            if (StringUtils.isNotEmpty(cmd)){
                if (cmd.startsWith("/in")){
                    doSignin(httpReq,httpResp,sm,session);
                    return;
                }

                if (cmd.startsWith("/out")){
                    doSignout(httpReq,httpResp,sm,session);
                    return;
                }

                if (cmd.startsWith("/up")){
                    doSignup(httpReq,httpResp,sm,session);
                    return;
                }

                if (cmd.startsWith("/ok")){
                    doSignok(httpReq,httpResp,sm,session);
                    return;
                }

                if (cmd.startsWith("/bind")){
                    doSignbind(httpReq,httpResp,sm,session);
                    return;
                }
            }

            if (session.isLoggedIn()){
                //已经登录
                chain.doFilter(request, response);
            }else{
                String returnUrl = httpReq.getRequestURI();
                String query = httpReq.getQueryString();
                if (StringUtils.isNotEmpty(query)){
                    returnUrl += "?" + query;
                }
                session.hSet(sessionGroup, this.arguReturn, returnUrl, true);
                doSignin(httpReq,httpResp,sm,session);
            }
        }catch (BaseException ex){
            httpClientTool.sendError(httpResp,E404,String.format("%s:%s",ex.getCode(),ex.getMessage()));
        }
    }

    private void doSignok(HttpServletRequest httpReq,
                          HttpServletResponse httpResp, SessionManager sm, Session session) {
        try {
            String nextUrl = session.hGet(sessionGroup, this.arguReturn, mainURL);
            if (onSignok != null){
                Context ctx = new HttpContext(httpReq,httpResp,encoding);
                LogicletContext logicletContext = new Context.ServantLogicletContext(ctx);
                CookieManager cm = new CookieManager.Default(sm, httpReq, httpResp);
                try {
                    logicletContext.setObject(ID_SESSION, session);
                    logicletContext.setObject(ID_COOKIES, cm) ;
                    logicletContext.SetValue("$service", "/sign/ok");
                    logicletContext.SetValue("$clientIp",httpClientTool.getClientIp(httpReq));
                    logicletContext.SetValue(arguNextUrl, nextUrl);
                    XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                    onSignok.execute(doc,doc, logicletContext, null);
                    nextUrl = PropertiesConstants.getString(logicletContext,arguNextUrl,nextUrl);
                }finally{
                    logicletContext.removeObject(ID_SESSION);
                    logicletContext.removeObject(ID_COOKIES);
                }
            }
            httpClientTool.sendRedirect(httpResp,nextUrl);
        } catch (IOException e) {
            LOG.error(e.getMessage());
        } finally{
            session.hDel(sessionGroup, this.arguReturn);
        }
    }

    private void doSignbind(HttpServletRequest httpReq,
                            HttpServletResponse httpResp, SessionManager sm, Session session) {
        try {
            String nextUrl = this.bindURL;
            if (onSignbind != null){
                Context ctx = new HttpContext(httpReq,httpResp,encoding);
                LogicletContext logicletContext = new Context.ServantLogicletContext(ctx);
                CookieManager cm = new CookieManager.Default(sm, httpReq, httpResp);
                try {
                    logicletContext.setObject(ID_SESSION, session);
                    logicletContext.setObject(ID_COOKIES, cm) ;
                    logicletContext.SetValue("$service", "/sign/bind");
                    logicletContext.SetValue("$clientIp",httpClientTool.getClientIp(httpReq));
                    logicletContext.SetValue(arguNextUrl, nextUrl);
                    XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                    onSignbind.execute(doc,doc, logicletContext, null);
                    nextUrl = PropertiesConstants.getString(logicletContext,arguNextUrl,nextUrl);
                }finally{
                    logicletContext.removeObject(ID_SESSION);
                    logicletContext.removeObject(ID_COOKIES);
                }
            }

            httpClientTool.sendRedirect(httpResp,nextUrl);
        } catch (IOException e) {
            LOG.error(e.getMessage());
        } finally{
            session.hDel(sessionGroup, this.arguReturn);
        }
    }

    private void doSignup(HttpServletRequest httpReq,
                          HttpServletResponse httpResp, SessionManager sm, Session session) {
        try {
            String nextUrl = this.registerURL;
            if (onSignup != null){
                Context ctx = new HttpContext(httpReq,httpResp,encoding);
                LogicletContext logicletContext = new Context.ServantLogicletContext(ctx);
                CookieManager cm = new CookieManager.Default(sm, httpReq, httpResp);
                try {
                    logicletContext.setObject(ID_SESSION, session);
                    logicletContext.setObject(ID_COOKIES, cm) ;
                    logicletContext.SetValue("$service", "/sign/up");
                    logicletContext.SetValue("$clientIp",httpClientTool.getClientIp(httpReq));
                    logicletContext.SetValue(arguNextUrl, nextUrl);
                    XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                    onSignup.execute(doc,doc, logicletContext, null);
                    nextUrl = PropertiesConstants.getString(logicletContext,arguNextUrl,nextUrl);
                }finally{
                    logicletContext.removeObject(ID_SESSION);
                    logicletContext.removeObject(ID_COOKIES);
                }
            }

            httpClientTool.sendRedirect(httpResp,nextUrl);
        } catch (IOException e) {
            LOG.error(e.getMessage());
        }
    }

    private void doSignout(HttpServletRequest httpReq,
                           HttpServletResponse httpResp, SessionManager sm, Session session) {
        try {
            String nextUrl = this.logoutURL;
            if (onSignout != null){
                Context ctx = new HttpContext(httpReq,httpResp,encoding);
                LogicletContext logicletContext = new Context.ServantLogicletContext(ctx);
                CookieManager cm = new CookieManager.Default(sm, httpReq, httpResp);
                try {
                    logicletContext.setObject(ID_SESSION, session);
                    logicletContext.setObject(ID_COOKIES, cm) ;
                    logicletContext.SetValue("$service", "/sign/out");
                    logicletContext.SetValue("$clientIp",httpClientTool.getClientIp(httpReq));
                    logicletContext.SetValue(arguNextUrl, nextUrl);
                    XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                    onSignout.execute(doc,doc, logicletContext, null);
                    nextUrl = PropertiesConstants.getString(logicletContext,arguNextUrl,nextUrl);
                }finally{
                    logicletContext.removeObject(ID_SESSION);
                    logicletContext.removeObject(ID_COOKIES);
                }
            }

            httpClientTool.sendRedirect(httpResp,nextUrl);
        } catch (IOException e) {
            LOG.error(e.getMessage());
        }
    }

    protected void doSignin(HttpServletRequest httpReq,
                            HttpServletResponse httpResp, SessionManager sm, Session session) {
        try {
            String returnUrl = getParameter(httpReq,this.arguReturn,"");
            if (StringUtils.isNotEmpty(returnUrl)){
                //将returnUrl保存在会话中
                session.hSet(sessionGroup, this.arguReturn, returnUrl, true);
            }

            String nextUrl = this.loginURL;

            if (onSignin != null){
                Context ctx = new HttpContext(httpReq,httpResp,encoding);
                LogicletContext logicletContext = new Context.ServantLogicletContext(ctx);
                CookieManager cm = new CookieManager.Default(sm, httpReq, httpResp);
                try {
                    logicletContext.setObject(ID_SESSION, session);
                    logicletContext.setObject(ID_COOKIES, cm) ;
                    logicletContext.SetValue("$service", "/sign/in");
                    logicletContext.SetValue("$clientIp",httpClientTool.getClientIp(httpReq));
                    logicletContext.SetValue(arguNextUrl, nextUrl);
                    XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                    onSignin.execute(doc,doc, logicletContext, null);
                    nextUrl = PropertiesConstants.getString(logicletContext,arguNextUrl,nextUrl);
                }finally{
                    logicletContext.removeObject(ID_SESSION);
                    logicletContext.removeObject(ID_COOKIES);
                }
            }

            httpClientTool.sendRedirect(httpResp,nextUrl);
        } catch (IOException e) {
            LOG.error(e.getMessage());
        }
    }

    @Override
    public void destroy() {

    }

    /**
     * 通过URI计算出当前的command
     * @param uri URI
     * @return cmd
     */
    protected String getCommand(String uri){
        if (uri.startsWith(cmdPrefix)){
            return uri.substring(cmdPrefix.length());
        }else{
            return "";
        }
    }

    protected String getParameter(HttpServletRequest request, String id, String dftValue){
        String value = request.getParameter(id);
        return StringUtils.isEmpty(value)?dftValue:value;
    }

}